#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/objdetect.hpp"
#include <dirent.h>
#include <iostream>

#include "utils/cm_logprint.h"

CMLogPrint logPrint;

using namespace std;
using namespace cv;

static void help()
{
    cout << "\nThis program demonstrates the use of cv::CascadeClassifier "
            "class to detect objects (Face + eyes). You can use Haar or LBP "
            "features.\n"
            "This classifier can recognize many kinds of rigid objects, once "
            "the appropriate classifier is trained.\n"
            "It's most known use is for faces.\n"
            "Usage:\n"
            "./facedetect [--cascade=<cascade_path> this is the primary "
            "trained classifier such as frontal face]\n"
            "   [--nested-cascade[=nested_cascade_path this an optional "
            "secondary classifier such as eyes]]\n"
            "   [--scale=<image scale greater or equal to 1, try 1.3 for "
            "example>]\n"
            "   [--try-flip]\n"
            "   [filename|camera_index]\n\n"
            "see facedetect.cmd for one call:\n"
            "./facedetect "
            "--cascade=\"data/haarcascades/haarcascade_frontalface_alt.xml\" "
            "--nested-cascade=\"data/haarcascades/"
            "haarcascade_eye_tree_eyeglasses.xml\" --scale=1.3\n\n"
            "During execution:\n\tHit any key to quit.\n"
            "\tUsing OpenCV version "
         << CV_VERSION << "\n"
         << endl;
}

void detectAndDraw(Mat &img, CascadeClassifier &cascade,
                   CascadeClassifier &nestedCascade, double scale,
                   bool tryflip);

string        cascadeName;
string        nestedCascadeName;
static string dirpath = "";

static int imgindex = 0;

int scan_dir(string dirpath, CascadeClassifier cascade,
             CascadeClassifier nestedCascade, double scale, bool tryflip)
{
    struct dirent **dirent;
    int             n = scandir(dirpath.c_str(), &dirent, NULL, NULL);
    if (n <= 2)
        return 0; // . ..
    int i;
    printf("find count [%d]\n", n);
    for (i = 0; i < n; i++)
    {
        if (dirent[i]->d_name[0] == '.')
            continue;
        if (dirent[i]->d_type == DT_DIR)
        {
            char newpath[512] = {0};
            sprintf(newpath, "%s/%s", dirpath.c_str(), dirent[i]->d_name);
            scan_dir(newpath, cascade, nestedCascade, scale, tryflip);
        }
        else
        {
            char *endp = rindex(dirent[i]->d_name, '.');
            if (endp && strncmp(endp + 1, "jpg", 3) == 0)
            {
                char fullname[512] = {0};
                sprintf(fullname, "%s/%s", dirpath.c_str(), dirent[i]->d_name);
                printf("start to parse [%s]\n", fullname);
                Mat image =
                    imread(samples::findFileOrKeep(fullname), IMREAD_COLOR);
                detectAndDraw(image, cascade, nestedCascade, scale, tryflip);
            }
            free(dirent[i]);
        }
    }
    free(dirent);
    return 0;
}

int main(int argc, const char **argv)
{
    VideoCapture      capture;
    Mat               frame, image;
    string            inputName;
    bool              tryflip;
    CascadeClassifier cascade, nestedCascade;
    double            scale;

    cv::CommandLineParser parser(
        argc, argv,
        "{help h||}"
        "{cascade|data/haarcascades/haarcascade_frontalface_alt.xml|}"
        "{nested-cascade|data/haarcascades/"
        "haarcascade_eye_tree_eyeglasses.xml|}"
        "{scale|1|}{try-flip||}{dir||}{@filename||}");
    if (parser.has("help"))
    {
        help();
        return 0;
    }
    cascadeName       = parser.get<string>("cascade");
    nestedCascadeName = parser.get<string>("nested-cascade");
    dirpath           = parser.get<string>("dir");
    scale             = parser.get<double>("scale");
    if (scale < 1)
        scale = 1;
    tryflip   = parser.has("try-flip");
    inputName = parser.get<string>("@filename");
    if (!parser.check())
    {
        parser.printErrors();
        return 0;
    }
    printf("use dir path [%s]\n", dirpath.c_str());
    if (!nestedCascade.load(samples::findFileOrKeep(nestedCascadeName)))
        cerr << "WARNING: Could not load classifier cascade for nested objects"
             << endl;
    if (!cascade.load(samples::findFile(cascadeName)))
    {
        cerr << "ERROR: Could not load classifier cascade" << endl;
        help();
        return -1;
    }
    if (inputName.empty() || (isdigit(inputName[0]) && inputName.size() == 1))
    {
        int camera = inputName.empty() ? 0 : inputName[0] - '0';
        if (!capture.open(camera))
        {
            cout << "Capture from camera #" << camera << " didn't work" << endl;
            return 1;
        }
    }
    else if (!inputName.empty())
    {
        image = imread(samples::findFileOrKeep(inputName), IMREAD_COLOR);
        if (image.empty())
        {
            if (!capture.open(samples::findFileOrKeep(inputName)))
            {
                cout << "Could not read " << inputName << endl;
                return 1;
            }
        }
    }
    else
    {
        image = imread(samples::findFile("lena.jpg"), IMREAD_COLOR);
        if (image.empty())
        {
            cout << "Couldn't read lena.jpg" << endl;
            return 1;
        }
    }

    if (dirpath.length() > 0)
    {
        printf("scan dir to parse\n");
        scan_dir(dirpath, cascade, nestedCascade, scale, tryflip);
        printf("scan dir done\n");

        return 0;
    }

    if (capture.isOpened())
    {
        cout << "Video capturing has been started ..." << endl;

        for (;;)
        {
            capture >> frame;
            if (frame.empty())
                break;

            Mat frame1 = frame.clone();
            detectAndDraw(frame1, cascade, nestedCascade, scale, tryflip);

            char c = (char)waitKey(10);
            if (c == 27 || c == 'q' || c == 'Q')
                break;
        }
    }
    else
    {
        cout << "Detecting face(s) in " << inputName << endl;
        if (!image.empty())
        {
            detectAndDraw(image, cascade, nestedCascade, scale, tryflip);
            waitKey(0);
        }
        else if (!inputName.empty())
        {
            /* assume it is a text file containing the
            list of the image filenames to be processed - one per line */
            FILE *f = fopen(inputName.c_str(), "rt");
            if (f)
            {
                char buf[1000 + 1];
                while (fgets(buf, 1000, f))
                {
                    int len = (int)strlen(buf);
                    while (len > 0 && isspace(buf[len - 1]))
                        len--;
                    buf[len] = '\0';
                    cout << "file " << buf << endl;
                    image = imread(buf, 1);
                    if (!image.empty())
                    {
                        detectAndDraw(image, cascade, nestedCascade, scale,
                                      tryflip);
                        char c = (char)waitKey(0);
                        if (c == 27 || c == 'q' || c == 'Q')
                            break;
                    }
                    else
                    {
                        cerr << "Aw snap, couldn't read image " << buf << endl;
                    }
                }
                fclose(f);
            }
        }
    }

    return 0;
}

void detectAndDraw(Mat &img, CascadeClassifier &cascade,
                   CascadeClassifier &nestedCascade, double scale, bool tryflip)
{
    double              t = 0;
    vector<Rect>        faces, faces2;
    const static Scalar colors[] = {Scalar(255, 0, 0),   Scalar(255, 128, 0),
                                    Scalar(255, 255, 0), Scalar(0, 255, 0),
                                    Scalar(0, 128, 255), Scalar(0, 255, 255),
                                    Scalar(0, 0, 255),   Scalar(255, 0, 255)};
    Mat                 gray, smallImg;

    cvtColor(img, gray, COLOR_BGR2GRAY);
    double fx = 1 / scale;
    resize(gray, smallImg, Size(), fx, fx, INTER_LINEAR_EXACT);
    equalizeHist(smallImg, smallImg);

    t = (double)getTickCount();
    cascade.detectMultiScale(smallImg, faces, 1.1, 2,
                             0
                                 //|CASCADE_FIND_BIGGEST_OBJECT
                                 //|CASCADE_DO_ROUGH_SEARCH
                                 | CASCADE_SCALE_IMAGE,
                             Size(30, 30));
    if (tryflip)
    {
        flip(smallImg, smallImg, 1);
        cascade.detectMultiScale(smallImg, faces2, 1.1, 2,
                                 0
                                     //|CASCADE_FIND_BIGGEST_OBJECT
                                     //|CASCADE_DO_ROUGH_SEARCH
                                     | CASCADE_SCALE_IMAGE,
                                 Size(30, 30));
        for (vector<Rect>::const_iterator r = faces2.begin(); r != faces2.end();
             ++r)
        {
            faces.push_back(Rect(smallImg.cols - r->x - r->width, r->y,
                                 r->width, r->height));
        }
    }
    t = (double)getTickCount() - t;
    printf("detection time = %g ms\n", t * 1000 / getTickFrequency());
    for (size_t i = 0; i < faces.size(); i++)
    {
        Rect         r = faces[i];
        Mat          smallImgROI;
        vector<Rect> nestedObjects;
        Point        center;
        Scalar       color = colors[i % 8];
        int          radius;

        Rect rto         = Rect(r);
        Mat  saveImg     = img(rto);
        char tmpname[32] = {0};
        sprintf(tmpname, "faces/%d.jpg", imgindex++);
        imwrite(tmpname, saveImg);
        printf("write image of %s\n", tmpname);

        double aspect_ratio = (double)r.width / r.height;
        if (0.75 < aspect_ratio && aspect_ratio < 1.3)
        {
            center.x = cvRound((r.x + r.width * 0.5) * scale);
            center.y = cvRound((r.y + r.height * 0.5) * scale);
            radius   = cvRound((r.width + r.height) * 0.25 * scale);
            circle(img, center, radius, color, 3, 8, 0);
        }
        else
            rectangle(img, Point(cvRound(r.x * scale), cvRound(r.y * scale)),
                      Point(cvRound((r.x + r.width - 1) * scale),
                            cvRound((r.y + r.height - 1) * scale)),
                      color, 3, 8, 0);
        if (nestedCascade.empty())
            continue;
        smallImgROI = smallImg(r);
        nestedCascade.detectMultiScale(smallImgROI, nestedObjects, 1.1, 2,
                                       0
                                           //|CASCADE_FIND_BIGGEST_OBJECT
                                           //|CASCADE_DO_ROUGH_SEARCH
                                           //|CASCADE_DO_CANNY_PRUNING
                                           | CASCADE_SCALE_IMAGE,
                                       Size(30, 30));
        for (size_t j = 0; j < nestedObjects.size(); j++)
        {
            Rect nr  = nestedObjects[j];
            center.x = cvRound((r.x + nr.x + nr.width * 0.5) * scale);
            center.y = cvRound((r.y + nr.y + nr.height * 0.5) * scale);
            radius   = cvRound((nr.width + nr.height) * 0.25 * scale);
            circle(img, center, radius, color, 3, 8, 0);
        }
    }
    // imwrite("result.jpg", img);
    // imshow( "result", img );
}
